package gui;

import ddf.minim.AudioListener;
import ddf.minim.AudioPlayer;
import ddf.minim.Playable;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Timer;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * JPanel for controlling playback of an audio stream.
 * 
 * @author  Stefan Marks
 * @version 1.0 - 15.05.2013: Created
 */
public class PlaybackControlPanel 
    extends JPanel
    implements AudioListener
{
    private static final int STEPS_PER_SECOND = 10;
    
    /**
     * Creates a new playback control panel.
     */
    public PlaybackControlPanel()
    {
        initComponents();
        
        source = null;
        updateControls();
        sldTime.addChangeListener(new SliderChangeListener());
    }

    /**
     * Attaches a playable object to this playback controller.
     * 
     * @param playable the playable object to control with this panel
     */
    public void attachToAudio(Playable playable)
    {
        this.source = playable;
        ((AudioPlayer) source).addListener(this);
        
        int len = source.length();
        sldTime.setMaximum(len * STEPS_PER_SECOND / 1000);
        sldTime.setValue(0);
        int idx = 0;
        while ( majTicks[idx] * 5 < len / 1000 ) { idx++; }
        sldTime.setMajorTickSpacing(majTicks[idx] * STEPS_PER_SECOND);
        sldTime.setMinorTickSpacing(minTicks[idx] * STEPS_PER_SECOND);
        int l = 0;
        Dictionary<Integer, JComponent> labels = new Hashtable<Integer, JComponent>();
        while ( l < len )
        {
            String  label = String.format("%d:%02d", (int) (l/(1000*60)), (l / 1000) % 60);
            Integer value = l * STEPS_PER_SECOND / 1000;
            labels.put(value, new JLabel(label));
            l += sldTime.getMajorTickSpacing() * 1000 / STEPS_PER_SECOND;
        }
        sldTime.setLabelTable(labels);
        lblTimecode.setText("0000.000");
        updateControls();
    }
    
    /**
     * Detaches the panel from the playable object.
     */
    public void detachFromAudio()
    {
        ((AudioPlayer) source).removeListener(this);
        this.source = null;
        updateControls();
        sldTime.setValue(0);
        lblTimecode.setText("----.---");
    }
    
    private void updateControls()
    {
        btnRewind.setEnabled(source != null);
        btnPlay.setVisible((source == null) || !source.isPlaying());
        btnPlay.setEnabled((source != null) && !source.isPlaying());
        btnStop.setVisible((source != null) && source.isPlaying());
        
        btnLoop.setIcon(pauseIcon[btnLoop.isSelected() ? 1 : 0]);
        
        sldTime.setEnabled(source != null);
    }
    
    public boolean isLooping()
    {
        return btnLoop.isSelected();
    }
    
    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents()
    {

        sldTime = new javax.swing.JSlider();
        javax.swing.JPanel pnlButtons = new javax.swing.JPanel();
        lblTimecode = new javax.swing.JLabel();
        btnRewind = new javax.swing.JButton();
        btnPlay = new javax.swing.JButton();
        btnStop = new javax.swing.JButton();
        pauseIcon = new ImageIcon[2];
        pauseIcon[0] = new ImageIcon(getClass().getResource("/gui/icons/loop.png"));
        pauseIcon[1] = new ImageIcon(getClass().getResource("/gui/icons/loop_on.png"));

        btnLoop = new javax.swing.JToggleButton();

        setLayout(new java.awt.BorderLayout());

        sldTime.setMajorTickSpacing(60);
        sldTime.setMinorTickSpacing(10);
        sldTime.setPaintLabels(true);
        sldTime.setPaintTicks(true);
        sldTime.setValue(0);
        add(sldTime, java.awt.BorderLayout.PAGE_START);

        pnlButtons.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

        lblTimecode.setFont(new java.awt.Font("Courier New", 1, 24)); // NOI18N
        lblTimecode.setText("0000:000");
        pnlButtons.add(lblTimecode);

        btnRewind.setIcon(new javax.swing.ImageIcon(getClass().getResource("/gui/icons/rewind.png"))); // NOI18N
        btnRewind.setToolTipText("Rewind");
        btnRewind.setMargin(new java.awt.Insets(3, 3, 3, 3));
        btnRewind.addActionListener(new java.awt.event.ActionListener()
        {
            public void actionPerformed(java.awt.event.ActionEvent evt)
            {
                btnRewindPressed(evt);
            }
        });
        pnlButtons.add(btnRewind);

        btnPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/gui/icons/play.png"))); // NOI18N
        btnPlay.setToolTipText("Play");
        btnPlay.setMargin(new java.awt.Insets(3, 3, 3, 3));
        btnPlay.addActionListener(new java.awt.event.ActionListener()
        {
            public void actionPerformed(java.awt.event.ActionEvent evt)
            {
                btnPlayPressed(evt);
            }
        });
        pnlButtons.add(btnPlay);

        btnStop.setIcon(new javax.swing.ImageIcon(getClass().getResource("/gui/icons/pause.png"))); // NOI18N
        btnStop.setToolTipText("Pause");
        btnStop.setMargin(new java.awt.Insets(3, 3, 3, 3));
        btnStop.addActionListener(new java.awt.event.ActionListener()
        {
            public void actionPerformed(java.awt.event.ActionEvent evt)
            {
                btnStopPressed(evt);
            }
        });
        pnlButtons.add(btnStop);

        btnLoop.setIcon(pauseIcon[0]);
        btnLoop.setToolTipText("Loop the audio");
        btnLoop.setMargin(new java.awt.Insets(3, 3, 3, 3));
        btnLoop.addActionListener(new java.awt.event.ActionListener()
        {
            public void actionPerformed(java.awt.event.ActionEvent evt)
            {
                btnLoopActionPerformed(evt);
            }
        });
        pnlButtons.add(btnLoop);

        add(pnlButtons, java.awt.BorderLayout.PAGE_END);
    }// </editor-fold>//GEN-END:initComponents

    private void btnRewindPressed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnRewindPressed
    {//GEN-HEADEREND:event_btnRewindPressed
        sldTime.setValue(0);
        updateControls();
    }//GEN-LAST:event_btnRewindPressed

    private void btnPlayPressed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnPlayPressed
    {//GEN-HEADEREND:event_btnPlayPressed
        source.play();
        updateControls();
    }//GEN-LAST:event_btnPlayPressed

    private void btnStopPressed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnStopPressed
    {//GEN-HEADEREND:event_btnStopPressed
        source.pause();
        updateControls();
    }//GEN-LAST:event_btnStopPressed

    private void btnLoopActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_btnLoopActionPerformed
    {//GEN-HEADEREND:event_btnLoopActionPerformed
        updateControls();
    }//GEN-LAST:event_btnLoopActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JToggleButton btnLoop;
    private javax.swing.JButton btnPlay;
    private javax.swing.JButton btnRewind;
    private javax.swing.JButton btnStop;
    private javax.swing.JLabel lblTimecode;
    private javax.swing.JSlider sldTime;
    // End of variables declaration//GEN-END:variables

    @Override
    public void samples(float[] samp)
    {
        samples(samp, samp);
    }

    @Override
    public void samples(float[] sampL, float[] sampR)
    {
        updateFromPlayback = true;
        int pos = source.position();
        sldTime.setValue(pos * STEPS_PER_SECOND / 1000);
        lblTimecode.setText(
                String.format("%04d.%03d", 
                pos / 1000, pos % 1000));
        updateFromPlayback = false;

        if ( source.position() == source.length() )
        {
            // end of song reached
            if ( isLooping() )
            {
                source.play(0);
            }
            else
            {
                sldTime.setValue(0);
                updateControls();
            }
        }
    }

    private class SliderChangeListener implements ChangeListener
    {
        @Override
        public void stateChanged(ChangeEvent evt)
        {
            if ( updateFromPlayback ) return; // prevent infinte loop

            int pos = sldTime.getValue() * 1000 / STEPS_PER_SECOND;
            if ( source != null )
            {
                if ( source.isPlaying() )
                {
                    // moved while playing: play from new position
                    source.play(pos);
                }
                else                   
                {
                    // moved in pause mode: prepare new start position
                    source.cue(pos);
                }
            }
        }
    }

    private Playable source;
    private Timer    updateTimer;
    private boolean  updateFromPlayback; 
    
    private ImageIcon[] pauseIcon;        
    
    private final int[] majTicks = {5, 10, 60, 120, 240, 300, 600, 1200};
    private final int[] minTicks = {1,  1,  5,  10,  20,  30,  60,  120};
}
